package org.fjsei.yewu.aop.hibernate;

import org.fjsei.yewu.filter.Node;
import org.hibernate.Session;
import org.hibernate.search.engine.backend.document.DocumentElement;
import org.hibernate.search.engine.backend.document.IndexFieldReference;
import org.hibernate.search.engine.backend.document.IndexObjectFieldReference;
import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaObjectField;
import org.hibernate.search.engine.backend.types.Aggregable;
import org.hibernate.search.engine.backend.types.IndexFieldType;
import org.hibernate.search.engine.backend.types.Projectable;
import org.hibernate.search.engine.backend.types.Sortable;
import org.hibernate.search.mapper.orm.HibernateOrmExtension;
import org.hibernate.search.mapper.pojo.bridge.PropertyBridge;
import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext;
import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.PropertyBinder;
import org.hibernate.search.mapper.pojo.bridge.runtime.PropertyBridgeWriteContext;
import org.hibernate.search.mapper.pojo.model.PojoModelProperty;

import jakarta.persistence.EntityManager;
import jakarta.persistence.Tuple;
import jakarta.persistence.TypedQuery;
import java.util.UUID;


/** 提高性能：不要多余的sql查询语句，没必要查询每一个具体关联对象表。
 * 代替掉 @IndexedEmbedded(includePaths = {"id",“name”} )   Village vlg;
 * 对象属性桥接 hibernateSearch；
 * 自动重建索引时刻，发现关联查询没啥用处的查了很多字段。如何能够避免，提高性能！
 * 前提条件是：相应字段的(fetch = FetchType.LAZY) 还需要配合LAZY才能发挥出优点，若是设置成EAGER反而更慢了会查询2趟的。
 *【特别注意】Bridge这里若自己定义createQuery(jpql, Tuple.class)做得关联查询的话，必须把PropertyBinder注解的字段定义为FetchType.LAZY。
 * */
public class NodeIdNameBinder implements PropertyBinder {
    //目标索引字段名。默认，下面替换为注解的实际字段名。
    private String fieldName = "object";

    public NodeIdNameBinder fieldName(String fieldName) {
        this.fieldName = fieldName;
        return this;
    }

    @Override
    public void bind(PropertyBindingContext context) {
        //确保关联属性被修改时，涉及ES索引也能够修改。
        context.dependencies()
                .use( "id" )
                .use( "name" );

        PojoModelProperty bridgedElement = context.bridgedElement();
        IndexSchemaObjectField embedField = context.indexSchemaElement()
                .objectField( bridgedElement.name() );

        PojoModelProperty  idProperty= bridgedElement.property("id");
        boolean isLongType= idProperty.isAssignableTo(Long.class);      //自适应ID类型的

        IndexFieldType<String> nameFieldType = context.typeFactory().asString().analyzer("default")
                .projectable(Projectable.YES)
                .toIndexFieldType();
        //两种情况：NodeIdBinder自动适应Long和UUID 类型的ID； ES存储不同类型。
        if(isLongType){
            IndexFieldType<Long> longidFieldType = context.typeFactory().asLong()
                    .aggregable(Aggregable.YES).sortable(Sortable.YES)
                    .projectable(Projectable.YES)
                    .toIndexFieldType();

            context.bridge(Node.class, new LongBridge(
                    embedField.toReference(),
                    embedField.field("id", longidFieldType).toReference(),
                    embedField.field("name", nameFieldType).toReference()
            ));
        }
        else {
            IndexFieldType<String> uuidFieldType = context.typeFactory().asString()
                    .aggregable(Aggregable.YES).sortable(Sortable.YES)
                    .projectable(Projectable.YES)
                    .toIndexFieldType();

            context.bridge(Node.class, new Bridge(
                    embedField.toReference(),
                    embedField.field("id", uuidFieldType).toReference(),
                    embedField.field("name", nameFieldType).toReference()
            ));
        }
    }
    /*正常ID 是UUID版本的；
    * */
    @SuppressWarnings("rawtypes")
    private static class Bridge implements PropertyBridge<Node> {
        private final IndexObjectFieldReference embedField;       //关联对象字段本身
        private final IndexFieldReference<String> uuidField;       //关联对象字段底下的多个字段
        private final IndexFieldReference<String> nameField;
        private Bridge(IndexObjectFieldReference embedField,
                       IndexFieldReference<String> uuidField,
                       IndexFieldReference<String> nameField) {
            this.embedField = embedField;
            this.uuidField = uuidField;
            this.nameField = nameField;
        }
        /*针对是关联对象且 fetch= FetchType.LAZY)的才有意义的。Uunode就不会自动查询关联对象了。
        bridgedElement的类型: ID类型是UUID,在ES索引库存储都会变为字符串。 #Enum类型-数据库存储是Number类型, ES却是Keyword字符串的类型，
        Long类型ID的话，ES库可以用 long 做存储。
        如果定义为 NodeName bridgedElement,类型的：进入write()回调以前就已经读取from Village village0_ where village0_.id=全表了。还是改成Node bridgedElement,定义类型吧。
        * */
        @Override
        public void write(DocumentElement target, Node bridgedElement, PropertyBridgeWriteContext context) {
            if(null==bridgedElement)    return;
            UUID nodeId= (UUID) bridgedElement.getId();
            if(null==nodeId)    return;
            Session session = context.extension( HibernateOrmExtension.get() ).session();
            String jpql="select name from Village where id=:id";
            TypedQuery<Tuple> simpleQuery= ((EntityManager) session).createQuery(jpql, Tuple.class);
            simpleQuery.setParameter("id",nodeId);
            Tuple  rowDat= simpleQuery.getSingleResult();
            String name= (String) rowDat.get(0);
            DocumentElement top = target.addObject( this.embedField );
            top.addValue(this.uuidField, nodeId.toString());
            if(null!=name)
                top.addValue(this.nameField, name);
        }
    }
    //遗留Long ID版本
    private static class LongBridge implements PropertyBridge<Node> {
        private final IndexObjectFieldReference embedField;       //关联对象字段本身
        private final IndexFieldReference<Long> longidField;       //关联对象字段底下的多个字段
        private final IndexFieldReference<String> nameField;
        private LongBridge(IndexObjectFieldReference embedField,
                           IndexFieldReference<Long> longidField,
                           IndexFieldReference<String> nameField) {
            this.embedField = embedField;
            this.longidField = longidField;
            this.nameField = nameField;
        }
        /*针对是关联对象且 fetch= FetchType.LAZY)的才有意义的。Uunode就不会自动查询关联对象了。
        bridgedElement的类型: ID类型是UUID,在ES索引库存储都会变为字符串。 #Enum类型-数据库存储是Number类型, ES却是Keyword字符串的类型，
        Long类型ID的话，ES库可以用 long 做存储。
        * */
        @Override
        public void write(DocumentElement target, Node bridgedElement, PropertyBridgeWriteContext context) {
            if(null==bridgedElement)    return;
            Long nodeId= (Long) bridgedElement.getId();
            if(null==nodeId)    return;
            Session session = context.extension( HibernateOrmExtension.get() ).session();
            String jpql="select name from Village where id=:id";
            TypedQuery<Tuple> simpleQuery= ((EntityManager) session).createQuery(jpql, Tuple.class);
            simpleQuery.setParameter("id",nodeId);
            Tuple  rowDat= simpleQuery.getSingleResult();
            String name= (String) rowDat.get(0);
            DocumentElement top = target.addObject( this.embedField );
            top.addValue(this.longidField, (Long)nodeId );
            if(null!=name)
                top.addValue(this.nameField, name);
        }
    }
}

/*旧的  Village vlg;映射
    "vlg": {
      "dynamic": "strict",
      "properties": {
        "id": {
          "type": "keyword"
        },
        "name": {
          "type": "text"
        }
      }
    }
* */
